#include "zucmodule_error.h"
#include "zucmodule_exceptions.h"

#include "nml_intf/zuc_nml.hh"
#include "nml_oi.hh"

struct pyErrorChannel
{
    PyObject_HEAD NML* c;
};

static int Error_init(pyErrorChannel* self, PyObject* a, PyObject* k)
{
    const char* file = ZUC_DEFAULT_NMLFILE;
    if (file == NULL)
        return -1;

    NML* c = new NML(zucFormat, "zucError", "xzuc", file);
    if (!c)
    {
        PyErr_Format(Exception_GetBaseError(), "new NML failed");
        return -1;
    }

    self->c = c;
    return 0;
}

static PyObject* Error_poll(pyErrorChannel* s)
{
    if (!s->c->valid())
    {
        PyErr_Format(Exception_GetBaseError(), "Error buffer invalid");
        return NULL;
    }
    NMLTYPE type = s->c->read();
    if (type == 0)
    {
        Py_INCREF(Py_None);
        return Py_None;
    }

    PyObject* r = PyTuple_New(3);  // type, errcode, string
    PyTuple_SET_ITEM(r, 0, PyLong_FromLong(type));
#define PASTE(a, b) a##b
#define _TYPECASE(tag, type, ecode, f)                                       \
    case tag: {                                                              \
        char error_string[LINELEN];                                          \
        strncpy(error_string, ((type*)s->c->get_address())->f, LINELEN - 1); \
        error_string[LINELEN - 1] = 0;                                       \
        PyTuple_SET_ITEM(r, 1, PyLong_FromLong(ecode));                      \
        PyTuple_SET_ITEM(r, 2, PyUnicode_FromString(error_string));          \
        break;                                                               \
    }
#define TYPECASE(x, ecode, f) _TYPECASE(PASTE(x, _TYPE), x, ecode, f)
    switch (type)
    {
        TYPECASE(ZUC_OPERATOR_ERROR, ((ZUC_OPERATOR_ERROR*)s->c->get_address())->errcode, error)
        TYPECASE(ZUC_OPERATOR_TEXT, ((ZUC_OPERATOR_TEXT*)s->c->get_address())->id, text)
        TYPECASE(ZUC_OPERATOR_DISPLAY, ((ZUC_OPERATOR_DISPLAY*)s->c->get_address())->id, display)
        TYPECASE(NML_ERROR, 0, error)
        TYPECASE(NML_TEXT, 0, text)
        TYPECASE(NML_DISPLAY, 0, display)
    default: {
        char error_string[256];
        sprintf(error_string, "unrecognized error %" PRId32, type);
        PyTuple_SET_ITEM(r, 1, PyLong_FromLong(0));
        PyTuple_SET_ITEM(r, 2, PyUnicode_FromString(error_string));
        break;
    }
    }
    return r;
}

static void Error_dealloc(PyObject* self)
{
    delete ((pyErrorChannel*)self)->c;
    PyObject_Del(self);
}

static PyMethodDef Error_methods[] = {{"poll", (PyCFunction)Error_poll, METH_NOARGS, "Poll for errors"}, {NULL}};

static PyTypeObject Error_Type = {
    .ob_base = PyVarObject_HEAD_INIT(NULL, 0).tp_name = "jkzuc.error", /*tp_name*/
    .tp_basicsize = sizeof(pyErrorChannel),                            /*tp_basicsize*/
    /* methods */
    .tp_dealloc = (destructor)Error_dealloc, /*tp_dealloc*/
    .tp_flags = Py_TPFLAGS_DEFAULT,          /*tp_flags*/
    .tp_methods = Error_methods,             /*tp_methods*/
    .tp_init = (initproc)Error_init,         /*tp_init*/
    .tp_new = PyType_GenericNew,             /*tp_new*/
};

int32_t Error_Ready() { return PyType_Ready(&Error_Type); }
PyObject* Error_CreateObject()
{
    PyObject* err = (PyObject*)&Error_Type;
    Py_XINCREF(err);
    return err;
}

int32_t Error_DestroyObject(PyObject* stat)
{
    Py_XDECREF((PyObject*)&Error_Type);
    return 0;
}

PyObject* Error_GetObject() { return (PyObject*)&Error_Type; }